package hr.irb.goTermList;

import hr.irb.geneOntology.GOTerm;
import hr.irb.geneOntology.GeneOntology;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * For each GO term (among a large list), finds a list of 'recommended' 
 * GO terms, which have (a) high semantic similarity with the first term, 
 * (b) high AUPRC scores for gene function prediction and (c) high information
 * content.
 * 
 * @author Fran Supek
 */
public class GoTermRecommender {
  
  /** File with the auprc scores per GO category. */
  public static final String listOfAuprcScores = 
          "C:/Users/Fran/Dropbox/GORBI/012345-data-AUC-AUPRC/"
          + "AllNamespacesTogether_ROC_evaluated_goref_20111017_pairs012345.txt";
  
  /**
   * Gene Ontology categories with information content below this value
   * are not considered.
   */ 
  public static double infoContentThreshold = 5;
  
  /**
   * Gene Ontology categories with AUPRC below this values are not considered
   * at all.
   */ 
  public static double auprcThreshold = 0.80;
  
  
  
  /** Main script. */
  public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException, SQLException {
    
    GeneOntology myGo = new GeneOntology("Z:/databases/GeneOntology/go_201204-termdb.obo-xml.gz");
    
    // 0.ser = whole UniProt
    GoTermSizes sizes = GoTermSizes.deserializeFromFile(
            new File("Z:/databases/EBI_GOA_mappings/goa_2012-04-02/goTermSizes/0.ser"));

    
    List<GOTerm> allGoTerms = new ArrayList<GOTerm>();
    // these lists contain only the candidates for recommendation (meeting IC and AUPRC criteria)
    Map<GOTerm, Double> auprc = new LinkedHashMap<GOTerm, Double>();
    // the rank is expressed as percentile (normalized to total # GO categories)
    Map<GOTerm, Double> rankByAuprc = new LinkedHashMap<GOTerm, Double>();
    
    // --- (1) load the AUPRC scores for all GO categories, and find ranks
    
    BufferedReader bufRdr = new BufferedReader(new FileReader(listOfAuprcScores));
    String line = null;
    while ((line = bufRdr.readLine()) != null) {      
      line = line.trim();
      if (line.isEmpty() || line.startsWith("GOindex")) {
        continue;
      }
      String[] cols = line.split("\t");
      int goIdx = Integer.parseInt(cols[0]);
      double curAuprc = Double.parseDouble(cols[1]);
      if ( myGo.get(goIdx) == null  ) {
        continue;
      }
      allGoTerms.add(myGo.get(goIdx));
      if ( curAuprc < auprcThreshold ) {  // we're not at all interested in the low AUPRC terms
        continue;
      }
      if ( sizes.getFreqGuessIfUnknown(goIdx, myGo) > Math.pow(2, -infoContentThreshold) ) {  // too general GO terms
        continue;
      }
      auprc.put( myGo.get(goIdx) , curAuprc);
    }
    bufRdr.close();
    auprc = sortByValue(auprc);
    double normRank = 0.0;
    for ( GOTerm aGoTerm : auprc.keySet() ) {
      normRank += 1.0/auprc.size();
      rankByAuprc.put( aGoTerm, normRank ); // the last one should have rank 1.0 
    }

    //String driverName = "org.gjt.mm.mysql.Driver"; // MySQL MM JDBC driver
    //Class.forName(driverName);
    
    
    /* this is the SQL to create the table

    CREATE TABLE `recommender` (
      `idrecommender` int(11) NOT NULL AUTO_INCREMENT,
      `user_specified_GO_id` int(11) DEFAULT NULL,
      `recommended_GO_id` int(11) DEFAULT NULL,
      `AUPRC_rank` double DEFAULT NULL,
      `sem_simil_rank` double DEFAULT NULL,
      `joint_rank` double DEFAULT NULL,
      PRIMARY KEY (`idrecommender`),
      KEY `user_GO_id_index` (`user_specified_GO_id`)
    ) ENGINE=MyISAM DEFAULT CHARSET=latin1$$

     
    */ 
    Connection conn = DriverManager.getConnection(
            "jdbc:mysql://localhost:3306/gorbi_web_may2012_regenerated",
            "fran", "fran1081k");

    PreparedStatement stmt = conn.prepareStatement("INSERT INTO recommender " +
            "(user_specified_GO_id, recommended_GO_id, AUPRC_rank, sem_simil_rank, joint_rank)" +
            "VALUES (?, ?, ?, ?, ?)");
    
    
    
    
    
    // --- (2) loop through the GO categories one by one, and for each, find the 
    // semantic similarity to all other GO categories and find ranks
    
    for ( GOTerm aGoTerm : allGoTerms )  { // .... big outer loop, through all GOs (even those NOT meeting thresholds for IC and AUPRC)

      Map<GOTerm, Double> semSimil = new LinkedHashMap<GOTerm, Double>();
      // the rank is expressed as percentile (normalized to total # GO categories)
      Map<GOTerm, Double> rankBySimil = new LinkedHashMap<GOTerm, Double>();
      
      // find similarity to all other terms, and rank it
      int numBiolProc = 0, numCellComp = 0, numMolFunc = 0;
      for ( GOTerm aGoTermInner : auprc.keySet() ) {
        double simil = SemanticSimilarityScore.SIMREL.calculateDistance(aGoTerm, aGoTermInner, sizes, myGo); // if namespaces are different, similarity will be 0
        if ( aGoTerm == aGoTermInner ) {
          simil = 0; // trivial case of similarity to itself
        }
        semSimil.put( aGoTermInner, simil );
        numBiolProc += aGoTermInner.isBiologicalProcess() ? 1 : 0;
        numCellComp += aGoTermInner.isCellularComponent() ? 1 : 0;
        numMolFunc += aGoTermInner.isMolecularFunction() ? 1 : 0;
      }
      semSimil = sortByValue(semSimil);
      double normRankGo = 0.0;
      int totalNum = 0;
      if ( aGoTerm.isBiologicalProcess() )
        totalNum = numBiolProc;
      else if ( aGoTerm.isCellularComponent() ) 
        totalNum = numCellComp;
      else 
        totalNum = numMolFunc;
      for ( GOTerm aGoTermInner : semSimil.keySet() ) {
        if ( aGoTermInner.getNamespace() == aGoTerm.getNamespace() ) {
          normRankGo += 1.0/totalNum;
        }
        rankBySimil.put( aGoTermInner, normRankGo );  // the last one should have rank 1.0
      }
      
      // now, merge the ranks of the sem.similarity to ranks of the AUPRCs 
      // by taking the lower (worst) rank
      Map<GOTerm, Double> jointRanks = new LinkedHashMap<GOTerm, Double>();
      for ( GOTerm aGoTermInner : semSimil.keySet() ) {
        //jointRanks.put( aGoTermInner, Math.min( rankByAuprc.get(aGoTermInner), rankBySimil.get(aGoTermInner) ) );
        jointRanks.put( aGoTermInner, rankByAuprc.get(aGoTermInner) * rankBySimil.get(aGoTermInner) * rankBySimil.get(aGoTermInner) );
      }
      jointRanks = sortByValueDesc(jointRanks);  // high ranks first
      
      // print first five
      System.out.println("");
      System.out.println( "----- Recommended terms for GO term " + aGoTerm.toString() );
      int num = 0;
      for ( GOTerm aGoTermInner : jointRanks.keySet() ) {
        System.out.printf( "%s // AUPRC rank=%4.3f,  semantic simil rank=%4.3f,  joint rank=%4.3f", aGoTermInner.toString(), rankByAuprc.get(aGoTermInner), rankBySimil.get(aGoTermInner), jointRanks.get(aGoTermInner) );
        System.out.println("");
        num++;

        //PreparedStatement stmt = conn.prepareStatement("INSERT INTO recommender " +
        //        "(user_specified_GO_id, recommended_GO_id, AUPRC_rank, sem_simil_rank, joint_rank)" +
        //        "VALUES (?, ?, ?, ?, ?)");
        stmt.setInt(1, aGoTerm.getId());
        stmt.setInt(2, aGoTermInner.getId());
        stmt.setDouble(3, rankByAuprc.get(aGoTermInner));
        stmt.setDouble(4, rankBySimil.get(aGoTermInner));
        stmt.setDouble(5, jointRanks.get(aGoTermInner));
        stmt.executeUpdate();
        
        if ( num==5 ) {
          break;
        }
      }
      
      
      
      
    }  // ... end big outer loop ... (GO term by GO term)
    
    conn.close();
    
    
  }
  
  
  
  
  
  
  
  
  
  private static Map sortByValue(Map map) {
     List list = new LinkedList(map.entrySet());
     Collections.sort(list, new Comparator() {
          public int compare(Object o1, Object o2) {
               return ((Comparable) ((Map.Entry) (o1)).getValue())
              .compareTo(((Map.Entry) (o2)).getValue());
          }
     });

    Map result = new LinkedHashMap();
    for (Iterator it = list.iterator(); it.hasNext();) {
        Map.Entry entry = (Map.Entry)it.next();
        result.put(entry.getKey(), entry.getValue());
    }
    return result;
  } 

  private static Map sortByValueDesc(Map map) {
     List list = new LinkedList(map.entrySet());
     Collections.sort(list, new Comparator() {
          public int compare(Object o1, Object o2) {
               return - ( ((Comparable) ((Map.Entry) (o1)).getValue())
              .compareTo(((Map.Entry) (o2)).getValue()) );
          }
     });

    Map result = new LinkedHashMap();
    for (Iterator it = list.iterator(); it.hasNext();) {
        Map.Entry entry = (Map.Entry)it.next();
        result.put(entry.getKey(), entry.getValue());
    }
    return result;
  } 
  
  
}
